Skip to content

fix(app): optimize large review panes#35375

Open
Hona wants to merge 5 commits into
anomalyco:devfrom
Hona:review-pane-bench
Open

fix(app): optimize large review panes#35375
Hona wants to merge 5 commits into
anomalyco:devfrom
Hona:review-pane-bench

Conversation

@Hona

@Hona Hona commented Jul 5, 2026

Copy link
Copy Markdown
Member

Summary

  • replace the recursive review file tree with a normalized flat model and bounded TanStack virtualization
  • virtualize filtered review results while preserving path identity, focus, collapse state, and active-row scrolling
  • avoid expensive Kobalte tab-content observation and prewarm the selected diff worker pool
  • render small diffs directly while retaining Pierre virtualization for files above 500 lines
  • add production scaling benchmarks from 1 file / 100 changed lines through 10,000 files / 1,000,000 changed lines

Performance

10,000-file scenario Before After
Uncapped 40.29 MB payload 8.49 s 0.80 s median
VCS-capped 13.0 MB payload n/a 0.50 s median
Rendered tree rows 10,102 30
Maximum frame gap 7.03 s about 0.17-0.20 s capped

Scaling graph

The legend is rendered in the chart title. Values are stable render time in milliseconds; file counts progress logarithmically.

---
config:
  themeVariables:
    xyChart:
      plotColorPalette: "#e03131, #2f9e44"
---
xychart-beta
    title "Key: red = dev baseline; green = optimized"
    x-axis "Changed files" [1, 10, 100, 1000, 10000]
    y-axis "Stable render (ms)" 0 --> 9000
    line [254, 319, 353, 1153, 8487]
    line [174, 187, 204, 258, 805]
Loading
Files Dev baseline Optimized
1 254 ms 174 ms
10 319 ms 187 ms
100 353 ms 204 ms
1,000 1,153 ms 258 ms
10,000 8,487 ms 805 ms

The VCS-capped result uses REVIEW_PANE_PATCH_BYTE_LIMIT=10000000 to mirror the server aggregate patch cap. Broad filtering across 10,000 results stabilizes in about 50-100 ms.

Test plan

  • bun typecheck in packages/app
  • bun run typecheck:e2e in packages/app
  • bun typecheck in packages/session-ui
  • focused file-tree, path-normalization, and virtualization unit tests
  • review tab-switch, image, and line-comment Playwright regressions
  • production Playwright scaling benchmark, including three-repeat capped and uncapped 10,000-file runs
  • repository pre-push typecheck hook (30 packages)

@Hona Hona requested a review from Brendonovich as a code owner July 5, 2026 00:58
Copilot AI review requested due to automatic review settings July 5, 2026 00:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the performance and scalability of the v2 review pane by normalizing review file-tree data, applying bounded TanStack virtualization to the review sidebar + filtered file list, and reducing expensive UI observation work during tab switching. It also adds automated Playwright performance benchmarks to validate behavior from small reviews up through extreme 10k-file scenarios.

Changes:

  • Replace the recursive review file tree with a normalized, flattened model and TanStack virtualization for both the tree and filtered-results list.
  • Adjust review pane mounting/tab-panel behavior to avoid expensive tab-content observation, plus prewarm the diff worker pool.
  • Add/extend unit + Playwright regression tests and introduce a production scaling benchmark spec for large review panes.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/session-ui/src/v2/components/session-review-v2.tsx Prewarms Pierre diff worker pool when review mounts.
packages/session-ui/src/v2/components/session-review-file-preview-v2.tsx Adds heuristic to disable diff virtualization for smaller diffs.
packages/session-ui/src/v2/components/session-review-file-preview-v2-virtualize.ts Introduces shouldVirtualizeReviewDiff threshold logic.
packages/session-ui/src/v2/components/session-review-file-preview-v2-virtualize.test.ts Unit tests for the virtualization threshold helper.
packages/app/src/pages/session/v2/session-file-list-v2.tsx Virtualizes filtered review results list with focus retention + scroll-to-highlight.
packages/app/src/pages/session/v2/review-panel-v2.tsx Uses virtualized file list while searching; updates FileTreeV2 usage.
packages/app/src/pages/session/v2/review-diff-kinds.ts Centralizes path normalization via normalizeFileTreeV2Path.
packages/app/src/pages/session/v2/review-diff-kinds.test.ts Adds test coverage for file/directory path normalization.
packages/app/src/pages/session/session-side-panel.tsx Reworks review tabpanel mounting/ARIA to avoid costly observation; adds focusability hook.
packages/app/src/pages/session.tsx Wires reviewHasFocusableContent into SessionSidePanel.
packages/app/src/components/file-tree-v2.tsx Replaces recursive tree rendering with normalized model + TanStack virtualization.
packages/app/src/components/file-tree-v2-model.ts Adds normalized tree model builder + flattening for virtualization.
packages/app/src/components/file-tree-v2-model.test.ts Unit tests for sorting, collapsing, normalization, and deep paths.
packages/app/e2e/regression/review-tab-switch.spec.ts Extends regression to assert tab/tabpanel ARIA wiring.
packages/app/e2e/performance/timeline/review-pane-scaling-benchmark.spec.ts Adds scaling benchmark coverage (1 → 10,000 files; capped/uncapped payloads).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 15 to 21
import FileTree from "@/components/file-tree"
import { SessionContextUsage } from "@/components/session-context-usage"

const reviewTabID = "session-side-panel-review-tab"
const reviewTabPanelID = "session-side-panel-review-tabpanel"
import { SessionContextTab, SortableTab, FileVisual } from "@/components/session"
import { useCommand } from "@/context/command"

export function flattenFileTreeV2(model: FileTreeV2Model, expanded: (path: string) => boolean) {
const rows: FileTreeV2Row[] = []
const stack = (model.children.get("") ?? []).toReversed().map((node) => ({ node, level: 0 }))
Comment on lines +132 to 155
const model = createMemo(() => buildFileTreeV2Model(props.allowed ?? []))
const rows = createMemo(() => flattenFileTreeV2(model(), (path) => file.tree.state(path)?.expanded ?? true))
const [root, setRoot] = createSignal<HTMLDivElement>()
const [focused, setFocused] = createSignal<string>()
const virtualizer = createVirtualizer<HTMLDivElement, HTMLDivElement>({
get count() {
return rows().length
},
getScrollElement: () => root()?.closest<HTMLDivElement>(".scroll-view__viewport") ?? null,
estimateSize: () => 28,
gap: 2,
overscan: 10,
get getItemKey() {
const current = rows()
return (index: number) => current[index]?.node.path ?? index
},
rangeExtractor: (range) => {
const indexes = defaultRangeExtractor(range)
const path = focused()
const index = path ? rows().findIndex((row) => row.node.path === path) : -1
if (index < 0 || indexes.includes(index)) return indexes
return [...indexes, index].sort((a, b) => a - b)
},
})
Comment on lines +166 to +170
const rowByKey = createMemo(() => new Map(rows().map((row) => [row.node.path, row] as const)))
const virtualItemByKey = createMemo(
() => new Map(virtualizer.getVirtualItems().map((item) => [item.key, item] as const)),
)

const nodes = createMemo(() => visibleNodesForPath(props.path, file.tree.children, filter()))
const virtualRowKeys = createMemo(() => virtualizer.getVirtualItems().map((item) => item.key))
Comment on lines +64 to +68
rangeExtractor: (range) => {
const indexes = defaultRangeExtractor(range)
const path = focused()
const index = path ? props.files.indexOf(path) : -1
if (index < 0 || indexes.includes(index)) return indexes
Comment on lines +81 to +84
const virtualItemByKey = createMemo(
() => new Map(virtualizer.getVirtualItems().map((item) => [item.key, item] as const)),
)
const virtualRowKeys = createMemo(() => virtualizer.getVirtualItems().map((item) => item.key))
@Hona Hona added the beta label Jul 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants